/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this sample’s licensing information

Abstract:
Part of Core Audio AUBase Classes
*/

#ifndef __AUEffectBase_h__
#define __AUEffectBase_h__

#include "AUBase.h"
#include "AUSilentTimeout.h"
#include "CAException.h"

class AUKernelBase;

//  Base class for an effect with one input stream, one output stream,
//  any number of channels.
    /*! @class AUEffectBase */
class AUEffectBase : public AUBase {
public:
    /*! @ctor AUEffectBase */
                                AUEffectBase(   AudioComponentInstance      audioUnit,
                                                bool                        inProcessesInPlace = true );
    /*! @dtor ~AUEffectBase */
                                ~AUEffectBase();

    /*! @method Initialize */
    virtual OSStatus            Initialize();

    /*! @method Cleanup */
    virtual void                Cleanup();


    /*! @method Reset */
    virtual OSStatus            Reset(      AudioUnitScope              inScope,
                                            AudioUnitElement            inElement);

    /*! @method GetPropertyInfo */
    virtual OSStatus            GetPropertyInfo (AudioUnitPropertyID    inID,
                                            AudioUnitScope              inScope,
                                            AudioUnitElement            inElement,
                                            UInt32 &                    outDataSize,
                                            Boolean &                   outWritable);

    /*! @method GetProperty */
    virtual OSStatus            GetProperty (AudioUnitPropertyID        inID,
                                            AudioUnitScope              inScope,
                                            AudioUnitElement            inElement,
                                            void *                      outData);

    /*! @method SetProperty */
    virtual OSStatus            SetProperty(AudioUnitPropertyID         inID,
                                            AudioUnitScope              inScope,
                                            AudioUnitElement            inElement,
                                            const void *                inData,
                                            UInt32                      inDataSize);

    /*! @method StreamFormatWritable */
    virtual bool                StreamFormatWritable (AudioUnitScope    scope,
                                            AudioUnitElement            element);

    /*! @method ChangeStreamFormat */
    virtual OSStatus            ChangeStreamFormat (
                                        AudioUnitScope                      inScope,
                                        AudioUnitElement                    inElement,
                                        const CAStreamBasicDescription &    inPrevFormat,
                                        const CAStreamBasicDescription &    inNewFormat);

    /*! @method Render */
    virtual OSStatus    Render(AudioUnitRenderActionFlags &     ioActionFlags,
                                        const AudioTimeStamp &          inTimeStamp,
                                        UInt32                          inNumberFrames);

    // our virtual methods

    // If your unit processes N to N channels, and there are no interactions between channels,
    // it can override NewKernel to create a mono processing object per channel.  Otherwise,
    // don't override NewKernel, and instead, override ProcessBufferLists.
    /*! @method NewKernel */
    virtual AUKernelBase *      NewKernel() { return NULL; }

    /*! @method ProcessBufferLists */
    virtual OSStatus            ProcessBufferLists(
                                            AudioUnitRenderActionFlags &    ioActionFlags,
                                            const AudioBufferList &         inBuffer,
                                            AudioBufferList &               outBuffer,
                                            UInt32                          inFramesToProcess );

    // convenience format accessors (use output 0's format)
    /*! @method GetSampleRate */
    Float64                     GetSampleRate();

    /*! @method GetNumberOfChannels */
    UInt32                      GetNumberOfChannels();

    // convenience wrappers for accessing parameters in the global scope
    /*! @method SetParameter */
    using AUBase::SetParameter;
    void                        SetParameter(           AudioUnitParameterID            paramID,
                                                        AudioUnitParameterValue         value)
                                {
                                    Globals()->SetParameter(paramID, value);
                                }

    /*! @method GetParameter */
    using AUBase::GetParameter;
    AudioUnitParameterValue     GetParameter(           AudioUnitParameterID            paramID )
                                {
                                    return Globals()->GetParameter(paramID );
                                }

    /*! @method CanScheduleParameters */
    virtual bool                CanScheduleParameters() const { return true; }

    /*! @method IsBypassEffect */
    // This is used for the property value - to reflect to the UI if an effect is bypassed
    bool                        IsBypassEffect () { return mBypassEffect; }

protected:

    /*! @method MaintainKernels */
    void                        MaintainKernels();

    /*! @method ShouldBypassEffect */
    // This is used in the render call to see if an effect is bypassed
    // It can return a different status than IsBypassEffect (though it MUST take that into account)
    virtual bool                ShouldBypassEffect () { return IsBypassEffect(); }

public:
    /*! @method SetBypassEffect */
    virtual void                SetBypassEffect (bool inFlag) { mBypassEffect = inFlag; }

    /*! @method SetParamHasSampleRateDependency */
    void                        SetParamHasSampleRateDependency (bool inFlag)
                                {
                                    mParamSRDep = inFlag;
                                }

    /*! @method GetParamHasSampleRateDependency */
    bool                        GetParamHasSampleRateDependency () const { return mParamSRDep; }

    struct ScheduledProcessParams   // pointer passed in as void* userData param for ProcessScheduledSlice()
    {
        AudioUnitRenderActionFlags  *actionFlags;
        AudioBufferList             *inputBufferList;
        AudioBufferList             *outputBufferList;
    };

    virtual OSStatus            ProcessScheduledSlice(  void                *inUserData,
                                                        UInt32              inStartFrameInBuffer,
                                                        UInt32              inSliceFramesToProcess,
                                                        UInt32              inTotalBufferFrames );


    bool                            ProcessesInPlace() const {return mProcessesInPlace;};
    void                            SetProcessesInPlace(bool inProcessesInPlace) {mProcessesInPlace = inProcessesInPlace;};

    typedef std::vector<AUKernelBase *> KernelList;



protected:
    /*! @var mKernelList */
    KernelList                      mKernelList;

    AUKernelBase* GetKernel(UInt32 index) { return mKernelList[index]; }

    /*! @method IsInputSilent */
    bool                            IsInputSilent (AudioUnitRenderActionFlags   inActionFlags, UInt32 inFramesToProcess)
                                    {
                                        bool inputSilent = (inActionFlags & kAudioUnitRenderAction_OutputIsSilence) != 0;

                                        // take latency and tail time into account when propagating the silent bit
                                        UInt32 silentTimeoutFrames = UInt32(GetSampleRate() * (GetLatency() + GetTailTime()));
                                        mSilentTimeout.Process (inFramesToProcess, silentTimeoutFrames, inputSilent);
                                        return inputSilent;
                                    }

#if TARGET_OS_IPHONE
    void SetOnlyOneKernel(bool inUseOnlyOneKernel) { mOnlyOneKernel = inUseOnlyOneKernel; } // set in ctor of subclass that wants it.
#endif

    template <typename T>
    void    ProcessBufferListsT(
                                        AudioUnitRenderActionFlags &    ioActionFlags,
                                        const AudioBufferList &         inBuffer,
                                        AudioBufferList &               outBuffer,
                                        UInt32                          inFramesToProcess );

    CAStreamBasicDescription::CommonPCMFormat GetCommonPCMFormat() const { return mCommonPCMFormat; }


private:
    /*! @var mBypassEffect */
    bool                            mBypassEffect;
    /*! @var mParamSRDep */
    bool                            mParamSRDep;

    /*! @var mProcessesInplace */
    bool                            mProcessesInPlace;

    /*! @var mSilentTimeout */
    AUSilentTimeout                 mSilentTimeout;

    /*! @var mMainOutput */
    AUOutputElement *               mMainOutput;

    /*! @var mMainInput */
    AUInputElement *                mMainInput;

#if TARGET_OS_IPHONE
    /*! @var mOnlyOneKernel */
    bool                            mOnlyOneKernel;
#endif

    /*! @var mCommonPCMFormat */
    CAStreamBasicDescription::CommonPCMFormat       mCommonPCMFormat;
    UInt32                          mBytesPerFrame;
};


//  Base class for a "kernel", an object that performs DSP on one channel of an interleaved stream.
    /*! @class AUKernelBase */
class AUKernelBase {
public:
    /*! @ctor AUKernelBase */
                                AUKernelBase(AUEffectBase *inAudioUnit ) :
                                    mAudioUnit(inAudioUnit) { }

    /*! @dtor ~AUKernelBase */
    virtual                     ~AUKernelBase() { }

    /*! @method Reset */
    virtual void                Reset() { }

    /*! @method Process */
    virtual void                Process(    const Float32 *                     inSourceP,
                                            Float32 *                           inDestP,
                                            UInt32                              inFramesToProcess,
                                            UInt32                              inNumChannels,
                                            bool &                              ioSilence) { throw CAException(kAudio_UnimplementedError ); }

    /*! @method Process */
    virtual void                Process(    const SInt32 *                      inSourceP,
                                            SInt32 *                            inDestP,
                                            UInt32                              inFramesToProcess,
                                            UInt32                              inNumChannels,
                                            bool &                              ioSilence) { throw CAException(kAudio_UnimplementedError ); }

    /*! @method Process */
    virtual void                Process(    const SInt16 *                      inSourceP,
                                            SInt16 *                            inDestP,
                                            UInt32                              inFramesToProcess,
                                            UInt32                              inNumChannels,
                                            bool &                              ioSilence) { throw CAException(kAudio_UnimplementedError ); }

    /*! @method GetSampleRate */
    Float64                     GetSampleRate()
                                {
                                    return mAudioUnit->GetSampleRate();
                                }

    /*! @method GetParameter */
    AudioUnitParameterValue     GetParameter (AudioUnitParameterID  paramID)
                                {
                                    return mAudioUnit->GetParameter(paramID);
                                }

    void                        SetChannelNum (UInt32 inChan) { mChannelNum = inChan; }
    UInt32                      GetChannelNum () { return mChannelNum; }

protected:
    /*! @var mAudioUnit */
    AUEffectBase *      mAudioUnit;
    UInt32              mChannelNum;

};

template <typename T>
void    AUEffectBase::ProcessBufferListsT(
                                    AudioUnitRenderActionFlags &    ioActionFlags,
                                    const AudioBufferList &         inBuffer,
                                    AudioBufferList &               outBuffer,
                                    UInt32                          inFramesToProcess )
{
    bool ioSilence;

    bool silentInput = IsInputSilent (ioActionFlags, inFramesToProcess);
    ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence;

    // call the kernels to handle either interleaved or deinterleaved
    if (inBuffer.mNumberBuffers == 1) {
        if (inBuffer.mBuffers[0].mNumberChannels == 0)
            throw CAException(kAudio_ParamError);

        for (UInt32 channel = 0; channel < mKernelList.size(); ++channel) {
            AUKernelBase *kernel = mKernelList[channel];

            if (kernel == NULL) continue;
            ioSilence = silentInput;

            // process each interleaved channel individually
            kernel->Process(
                (const T *)inBuffer.mBuffers[0].mData + channel,
                (T *)outBuffer.mBuffers[0].mData + channel,
                inFramesToProcess,
                inBuffer.mBuffers[0].mNumberChannels,
                ioSilence);

            if (!ioSilence)
                ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence;
        }
    } else {
        for (UInt32 channel = 0; channel < mKernelList.size(); ++channel) {
            AUKernelBase *kernel = mKernelList[channel];

            if (kernel == NULL) continue;

            ioSilence = silentInput;
            const AudioBuffer *srcBuffer = &inBuffer.mBuffers[channel];
            AudioBuffer *destBuffer = &outBuffer.mBuffers[channel];

            kernel->Process(
                (const T *)srcBuffer->mData,
                (T *)destBuffer->mData,
                inFramesToProcess,
                1,
                ioSilence);

            if (!ioSilence)
                ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence;
        }
    }
}


#endif // __AUEffectBase_h__
